<?php
/**
 * @author Erling Owe <erling.owe@gmail.com>
 * @package Core
 * @subpackage Presentation
 */

namespace Core\Presentation\HTML;

/**
 * This is an implementation of NodeCollectionA.
 */
class NodeCollection extends \ArrayObject implements NodeCollectionI {

    private function interpretExpression ( NodeCollection $objNodeCollection, $strExpression ) {

        // The node collection we're gonna return.
        $objReturn = new NodeCollection ();

        // The expression may be several comma seperated selectors.
        $arrExpressions = explode( ",", $strExpression );
        foreach ( $arrExpressions as $strSelector ) {

            // Trim possible whitespaces of of the selector.
            $strSelector = trim( $strSelector );

            // Create a copy of $objNodeCollection to work with in this foreach() loop.
            $objNodeCollectionWorkingCopy = $objNodeCollection;

            
            // TODO: Find a proper comment to put here.
            if ( substr( $strSelector, 0, 1 ) == "#" ) {

                // NOTE: list() returns an error if $strSelectors is not set, hence the @.
                @list( $strId, $strSelector ) = explode( " ", substr( $strSelector, 1 ) );

                if ( !isset( $strId ) ) {
                    throw new \InvalidArgumentException ( "# must be directly preceeded by an actual id." );
                }

                // Find the element matching the id.
                foreach ( $objNodeCollection as $objNode ) {
                    if ( $objNode instanceof Element ) {
                        if ( $objNode->getAttribute( "id" ) == $strId ) {
                            $objMatch = $objNode;
                        }
                    }
                }

                // If no matching element was found, throw an exception.
                if ( !isset( $objMatch ) ) {
                    throw new \InvalidArgumentException ( "There's no element with the id $strId in the document." );
                }

                if ( trim( $strSelector ) ) {
                    // Fill the working copy of $objNodeCollection with only those nodes that are descendents of the one matching the id.
                    $objNodeCollectionWorkingCopy = $objMatch->find();
                } else {
                    // Add the element that matched the id to the return collection.
                    if ( !in_array( $objMatch, (array) $objReturn ) ) {
                        $objReturn[] = $objMatch;
                    }
                }

            }


            // Continue to interpret the selector.
            if ( $strSelector ) {

                $strPattern = "/([a-zA-Z\d_\-\.:]+)([\s]*(\s|>|\+|~)[\s]*){1}([a-zA-Z\d_\-\.:]+)/";
                preg_match( $strPattern, $strSelector, $arrMatches );

                if ( $arrMatches ) {

                    $strOperator = $arrMatches[3];

                    switch ( $strOperator ) {

                        case !trim( $strOperator ): // Equivalent to whitespace.

                            $strAncestor = $arrMatches[1];
                            $strDescendent = $arrMatches[4];

                            $objAncestorCollection = new NodeCollection ();
                            $objAncestorCollection = self::interpretSelector( $objNodeCollectionWorkingCopy, $strAncestor );

                            foreach ( $objAncestorCollection as $objAncestor ) {
                                
                                $objDescendentCollection = new NodeCollection ();
                                $objDescendentCollection = self::interpretSelector( $objAncestor->find(), $strDescendent );

                                //echo count( $objDescendentCollection ) . "|";

                                foreach ( $objDescendentCollection as $objNode ) {
                                    //if ( !in_array( $objNode, (array) $objReturn ) ) {
                                        $objReturn[] = $objNode; //echo $objNode;
                                    //}
                                }// echo count ( $objReturn );

                            }

                            break;

                        case "+":

                            $strPrevious = $arrMatches[1];
                            $strNext = $arrMatches[4];
                            //
                            break;

                        case ">":

                            $strParent = $arrMatches[1];
                            $strChild = $arrMatches[4];
                            //
                            break;

                        case "~":

                            $strPrevious = $arrMatches[1];
                            $strSibling = $arrMatches[4];
                            //
                            break;

                    }

                } else {
                    // There's no operator in the expression.
                    foreach ( $this->interpretSelector( $objNodeCollectionWorkingCopy, $strSelector ) as $objNode ) {
                        if ( !in_array( $objNode, (array) $objReturn ) ) {
                            $objReturn[] = $objNode;
                        }
                    }
                }

            }

        }

        return $objReturn;

    }

    /**
     * Interprets a selector without an operator. // TODO: Write in a nice way without the # thingy too.
     */
    private static function interpretSelector ( NodeCollection $objNodeCollection, $strSelector ) {

        $strPattern = "/([a-z]+[123456]*)?(\.([a-zA-Z\d_\-]+))?(:(odd|even){1})?/";
        preg_match( $strPattern, $strSelector, $arrMatches );

        $strElement = ( isset( $arrMatches[1] ) ) ? $arrMatches[1] : null;
        $strClass = ( isset ( $arrMatches[3] ) ) ? $arrMatches[3] : null;
        $strFilter = ( isset( $arrMatches[5] ) ) ? $arrMatches[5] : null;

        // Filter out the nodes that does not match the element.
        if ( $strElement ) {
            $objNodeCollectionFilteredByElement = new NodeCollectionB ();
            foreach ( $objNodeCollection as $objNode ) {
                if ( (string) $objNode == $strElement ) {
                    $objNodeCollectionFilteredByElement[] = $objNode;
                }
            }
            // We don't need the rest of the original node collection any more, so we'll just overwrite it.
            $objNodeCollection = $objNodeCollectionFilteredByElement;
        }

        // Filter out the nodes that does not match the class.
        if ( $strClass ) { 
            $objNodeCollectionFilteredByClass = new NodeCollection ();
            foreach ( $objNodeCollection as $objNode ) { 
                if ( $objNode->getAttribute( "class" ) == $strClass ) {
                    $objNodeCollectionFilteredByClass[] = $objNode;
                }
            }
            // We don't need the rest of the original node collection any more, so we'll just overwrite it.
            $objNodeCollection = $objNodeCollectionFilteredByClass;
        }

        // Filter out the nodes that does not match the filter.
        if ( $strFilter ) {

            $objNodeCollectionFilteredByFilter = new NodeCollection ();

            switch ( $strFilter ) {

                case "odd":

                    $t = false;
                    foreach ( $objNodeCollection as $objNode ) {
                        if ( $t ) {
                            $objNodeCollectionFilteredByFilter[] = $objNode;
                        }
                        $t = ( $t == false ) ? true : false;
                    }

                    break;

                case "even":

                    $t = true;
                    foreach ( $objNodeCollection as $objNode ) {
                        if ( $t ) {
                            $objNodeCollectionFilteredByFilter[] = $objNode;
                        }
                        $t = ( $t == false ) ? true : false;
                    }

                    break;

            }
            // We don't need the rest of the original node collection any more, so we'll just overwrite it.
            $objNodeCollection = $objNodeCollectionFilteredByFilter;
        }

        return $objNodeCollection;

    }

    /**
     * Gets all the descendents of a NodeCollectionB.
     */
    private static function getAllDescendents ( NodeCollection $objNodeCollection ) {

        $objReturn = new NodeCollection ();

        foreach ( $objNodeCollection as $objNode ) { 
            $objReturn[] = $objNode;
            if ( $objNode instanceof Element ) {
                foreach ( self::getAllDescendents( $objNode->children() ) as $objDescendent ) {
                    $objReturn[] = $objDescendent;
                }
            }
        }

        return $objReturn;

    }

    public function get ( $intIndex ) {
        return $this[$intIndex];
    }


    // Methods required by interface NodeCollectionA.

    /**
     * Gets all the nodes in the collection matching the expression.
     */
    public function all ( $strExpression = null ) {
        return ( $strExpression ) ? $this->interpretExpression( $this, $strExpression ) : $this;
    }

    /**
     * Gets all the children matching the expression.
     */
    public function children ( $strExpression = null ) {

        $objNodeCollection = new NodeCollectionB ();

        foreach ( $this as $objNode ) {
            if ( $objNode instanceof Element ) {
                foreach ( $objNode->children() as $objChildNode ) {
                    $objNodeCollection[] = $objChildNode ;
                }
            }
        }

        return ( $strExpression ) ? $this->interpreter( $objNodeCollection, $strExpression ) : $objNodeCollection;

    }

    /**
     * Gets all the descending nodes matching the expression.
     */
    public function find ( $strExpression = null ) {
        return ( $strExpression ) ? $this->interpretExpression( self::getAllDescendents( $this ), $strExpression ) : self::getAllDescendents( $this );
    }

    /**
     * Sets an attribute to all the element nodes in the collection.
     */
    public function setAttribute ( $strName, $strValue ) {
        foreach ( $this as $objNode ) {
            if ( $objNode instanceof Element ) {
                $objNode->setAttribute( $strName, $strValue );
            }
        }
    }

}

?>
